/*-------------------------------------------------*
 |  VisualK-OS FBA: Strobe Subsystem Manager class |
 |  Author: Kevin Slaughter                        |
 | Version: 3.0                                    |
 *-------------------------------------------------*/

/* Revision History
    v3.1: Finalized: No direct access to light manager, causes too many issues.
                     Light manager will check for and handle all commands.
                     * Began usage of objMinimalLight to shadow real lighting system.
    v3.0: Initial standardization, after many changes as development progressed.
*/

#ifndef STROBEMANAGER_H
#define STROBEMANAGER_H
  /* Includes */
  #include "CmdConstants.h"
  #include "LightManager.h"
  
  
  /* Constants */
  const unsigned int NUM_PROGS_STROBE = 7;
  const unsigned int STROBE_ALL_BOARDS = 9;
    
  
  // Strobe Manager class
  class StrobeManager {
    private:
      // Strobe program packet
      typedef struct {
        bool bWF;                 // True = White strobe | False = RGB strobe
        unsigned int iBoard;      // Board(s) affected by this strobe
        unsigned int iFreq;       // Strobe frequency (ms on/off)
        unsigned long lTime;      // Timer (current tick)
        unsigned long lLast;      // ^   ^ (last tick)
        unsigned int iProgColor;  // Color for strobe program, if applicable
        
        /* MACROS */
        void reset() {lTime = lLast = 0;}
      } prgStrobe;
      
      /* VARS */
      bool _active;                       // Status of strobe
      unsigned int _curProg;              // Current strobe mode
      prgStrobe _progs[NUM_PROGS_STROBE]; // Defined strobe modes
      unsigned int _numRGBs;              // Number of RGB lamps
      unsigned int _numWFs;               // Number of white floods
      bool _hasChanged;                   // Refresh indicator
      objLamp _lights[1];                 // Light states
      objLamp _lightsW[1];                // White flood states
      

    
    public:
      // Constructor; Takes number of RGB lamps and number of white floods
      StrobeManager(unsigned int iNumRGBs, unsigned int iNumWFs) {
        _active = false;  // Default to off
        _numRGBs = iNumRGBs;  // Define number of RGB LED lamps
        _numWFs = iNumWFs;    // ^      ^       ^ white flood lamps
        
        // Redimension array of shadow light objects
        realloc(_lights, _numRGBs*sizeof(objLamp));
        realloc(_lightsW, _numWFs*sizeof(objLamp));
        
        // Setup hardcoded program parameters
        for(unsigned int iP=0; iP<NUM_PROGS_STROBE; iP++) {
          // Populate program packets
          switch(iP) {
            // Program #0 - White, all boards, 50ms
            case 0:
              _progs[0].iFreq = 50;
              _progs[0].iBoard = STROBE_ALL_BOARDS;
              _progs[0].bWF = true;
              break;
            
            // Program #1 - White, all boards, 25ms
            case 1:
              _progs[1].iFreq = 25;
              _progs[1].iBoard = STROBE_ALL_BOARDS;
              _progs[1].bWF = true;
              break;
            
            // Program #2 - Red, all boards, 25ms
            case 2:
              _progs[2].iFreq = 25;
              _progs[2].iBoard = STROBE_ALL_BOARDS;
              _progs[2].bWF = false;
              _progs[2].iProgColor = VKC_RED;
              break;
              
            // Program #3 - Green, all boards, 25ms
            case 3:
              _progs[3].iFreq = 25;
              _progs[3].iBoard = STROBE_ALL_BOARDS;
              _progs[3].bWF = false;
              _progs[3].iProgColor = VKC_GREEN;
              break;
              
            // Program #4 - Blue, all boards, 25ms
            case 4:
              _progs[4].iFreq = 25;
              _progs[4].iBoard = STROBE_ALL_BOARDS;
              _progs[4].bWF = false;
              _progs[4].iProgColor = VKC_BLUE;
              break;
            
            // Program #5 - Orange, all boards, 25ms
            case 5:
              _progs[5].iFreq = 25;
              _progs[5].iBoard = STROBE_ALL_BOARDS;
              _progs[5].bWF = false;
              _progs[5].iProgColor = VKC_ORANGE;
              break;
            
            // Program #6 - Green, all boards, 25ms
            case 6:
              _progs[6].iFreq = 25;
              _progs[6].iBoard = STROBE_ALL_BOARDS;
              _progs[6].bWF = false;
              _progs[6].iProgColor = VKC_GREEN;
              break;
            
            // Default to program #0
            default:
              setProg(0);
              break;
          }
        }
      }
      
      
      
      /* ### MACROS */
      // Returns TRUE if a strobe program is actively running.
      bool enabled()                    {return _active;}
      
      // Returns current program's strobe frequency
      unsigned int getFreq()            {return _progs[_curProg].iFreq;}
      
      // Returns pointer to shadow RGB lamp objects
      objLamp* getLampStates() {_hasChanged = false; return _lights;}
      
      // Returns current program's RGB lamp color, if applicable
      unsigned int getProgColor()       {return _progs[_curProg].iProgColor;}
      
      // Returns the program's last tick [in essence, the last time update() was called; Uses millis()]
      unsigned long getTime()           {return _progs[_curProg].lTime;}
      
      // Returns the time of the programs's last state toggle
      unsigned long getLast()           {return _progs[_curProg].lLast;}
      
      // Current program uses RGB lamps if true, white floods if false
      bool isColored()                  {return (bool)((_progs[_curProg].bWF) ? false : true);}
      
      // Indicates that a lamp state has changed, so LightMan needs to refresh.
      bool needsRefresh()               {return _hasChanged;}
      
      // Returns the index of the board that the current program will affect.
      unsigned int targetBoard()        {return _progs[_curProg].iBoard;}
      
      // Returns TRUE if the last known tick/update() occurred longer ago than current program's frequency specification (eg: 45ms)
      bool timeToToggle()               {return (bool)((_progs[_curProg].lTime-_progs[_curProg].lLast) >= _progs[_curProg].iFreq);}
      
      // Returns TRUE if the current program is intended to work across all boards (ie, stage-wide color/white)
      bool usesAllBoards()              {return (bool)(_progs[_curProg].iBoard==STROBE_ALL_BOARDS);}
      
      
      
      /* ### METHODS */
      /*--------------------------------------*
       | Func: setProg(iProg)                 |
       | Desc: Set the active strobe program. |
       *--------------------------------------*/
      void setProg(unsigned int iP) {
        if(iP<NUM_PROGS_STROBE) {
          _curProg = iP;
          _progs[_curProg].reset();
        }
      }
            
      
      /*------------------------------------------*
       | Func: startProg()                        |
       | Desc: Starts the current strobe program. |
       *------------------------------------------*/
       void startProg() {
         _active = true;            // Activate strobe
         _progs[_curProg].reset();  // Reset timers
       }
       
       
      /*-----------------------------------------*
       | Func: stopProg()                        |
       | Desc: Stops the current strobe program. |
       *-----------------------------------------*/
      void stopProg() {
        _active = false;    // Deactivate strobe
        
        // Stop strobe... Methodology depends on type of strobe.
        for(int r=0; r<_numRGBs; r++) {_lights[r].iState = LS_OFF;}
        for(int w=0; w<_numWFs; w++)  {_lightsW[w].iState = LS_OFF;}
        if(isColored()) {
          // TODO: Implement
        }
        else {
          // TODO: Implement
        }
        
        // Flag for refresh
        _hasChanged = true;
      }


      /*--------------------*
       |Func: update()      |
       |Desc: Update timers |
       *--------------------*/
      void update() {
        if(_active) {
          
          _progs[_curProg].lTime = millis();  // Update timer
          
          // Toggle light state(s), if needed
          if(timeToToggle()) {
             _progs[_curProg].lLast = millis();      // Update time last triggered
             _hasChanged = true;
                        
            // Color strobe
            if(isColored()) {
              // All lamps
              if(usesAllBoards()) {
                for(unsigned int rs=0; rs<_numRGBs; rs++) {
                  _lights[rs].iState = (_lights[rs].iState==LS_ON) ? LS_OFF : LS_ON;
                }
              }
              else {  // Individual board
                int iO = (targetBoard());
                for(unsigned int s=0; s<4; s++) {
                  _lights[iO+s].iState = (_lights[iO+s].iState==LS_ON) ? LS_OFF : LS_ON;
                }
              }
            }
            else {  // White strobe
              // All boards
              if(usesAllBoards()) {
                for(int a=0; a<_numWFs; a++) {_lightsW[a].iState = LS_ON;}
              }
              // Single board
              else {_lightsW[targetBoard()].iState = LS_ON;}
            }
          }
        }
      }
  };
#endif  
